package app.server.project.main.service.service.luck;

import app.server.project.core.constants.Def;
import app.server.project.core.constants.TcpCmd;
import app.server.project.core.websocket.WSMgr;
import app.server.project.main.service.bo.event.CreateLuckOrderEvent;
import app.server.project.main.service.bo.event.RechargeSuccessEvent;
import app.server.project.main.service.constants.LuckType;
import app.server.project.main.service.db.dao.user.UserActiveRewardDao;
import app.server.project.main.service.db.dao.user.UserInfoDao;
import app.server.project.main.service.db.dao.user.UserLuckOrderDao;
import app.server.project.main.service.db.entity.user.UserActiveRewardEntity;
import app.server.project.main.service.db.entity.user.UserLuckOrderEntity;
import app.server.project.main.service.dto.CommonLuckOrderDto;
import app.server.project.main.service.dto.MyLuckOrderDto;
import app.server.project.main.service.dto.SOLRewardRateDto;
import app.server.project.main.service.dto.race.RaceValDto;
import app.server.project.main.service.dto.solanainfo.SlotResp;
import app.server.project.main.service.service.recharge.CallbackType;
import cn.hutool.core.util.NumberUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.CopyOnWriteArrayList;

@RequiredArgsConstructor
@Service
@Slf4j
public class SOLRateService {

    private final static long Max = 100;
    private final UserLuckOrderDao userLuckOrderDao;
    private final UserActiveRewardDao userActiveRewardDao;

    private final UserInfoDao userInfoDao;
    private ConcurrentLinkedDeque<UserLuckOrderEntity> deque = new ConcurrentLinkedDeque<>();
    private CopyOnWriteArrayList<UserLuckOrderEntity> successLst = new CopyOnWriteArrayList<>();


    public static void main(String[] args) {
        System.out.println(LocalDateTime.now().plusHours(67));
    }

    @EventListener(CreateLuckOrderEvent.class)
    public void onCreateOrder(CreateLuckOrderEvent event) {
        if (event.getType() != LuckType.Rate) return;
        UserLuckOrderEntity userLuckOrderEntity = UserLuckOrderEntity.of(event.getOrderId());
        userLuckOrderEntity.setChoice(event.getChoice());
        userLuckOrderEntity.setUserId(event.getUserId());

        userLuckOrderEntity.setOrderType(event.getType());
        //刷新缓存
        userLuckOrderDao.updateOrder(userLuckOrderEntity);
    }

    @EventListener(RechargeSuccessEvent.class)
    public void onRechargeSuccess(RechargeSuccessEvent event) {
        if (event.getCallbackType() != CallbackType.SOLRate) return;
        var order = userLuckOrderDao.getOrder(event.getOrder().getId());
        if (order.getId() == Def.Zero) return;
        if (order.getOrderType() != LuckType.Rate) return;
        if (event.getCreateTime() > Def.Zero) {
            LocalDateTime localDateTime = Instant.ofEpochSecond(event.getCreateTime())
                    .atZone(ZoneId.systemDefault())
                    .toLocalDateTime();
            order.setSuccessTime(localDateTime);
            order.setSuccessUnixTime(event.getCreateTime());
        } else {
            order.setSuccessTime(LocalDateTime.now());
            order.setSuccessUnixTime(System.currentTimeMillis() / 1000);
        }
        order.setParam(event.getOrder().getConsumeNum() * LuckType.TonRate / Def.Ton);
        //开始进入匹配队列
        deque.add(order);
        var lst = this.userLuckOrderDao.getAll(order.getUserId(), LuckType.Rate, Max);
        lst.removeIf(item -> item.getId() == order.getId());
        lst.add(Def.Zero, order);
        if (lst.size() > Max) {
            var temp = lst.subList((int) Max, lst.size());
            lst.removeAll(temp);
        }
    }


    public void init() {
        var all = userLuckOrderDao.getAllUnSuccess(LuckType.Rate);
        this.deque.addAll(all);
        this.successLst.addAll(this.userLuckOrderDao.getAllLuck(LuckType.Rate, Max));
    }

    public List<MyLuckOrderDto> getMyAllRateSOL(long userId) {
        var lst = this.userLuckOrderDao.getAll(userId, LuckType.Rate, Max);
        return lst.stream().map(item -> {
            var ret = MyLuckOrderDto.of(item, LuckType.TonRate);
            if (ret.getOrder().getSuccessTime() != null) {
                ret.getOrder().setSuccessTimeStr(ret.getOrder().getSuccessTime().plusHours(4));
            }
            return ret;
        }).toList();
    }

    public List<CommonLuckOrderDto> getAllSOLRateCommon() {
        return successLst.stream().map(item -> {
            if (item.getSuccessTime() != null) {
                item.setSuccessTimeStr(item.getSuccessTime().plusHours(4));
            }
            return CommonLuckOrderDto.of(item, LuckType.TonRate, this.userInfoDao.getUserInfoById(item.getUserId()).getNick());
        }).toList();
    }

    public boolean IsEmpty() {
        return this.deque.isEmpty();
    }

    public void matchOrder(SlotResp resp) {
        var firstOrder = deque.pollFirst();
        if (firstOrder == null) return;
        int max = 1000;
        //var failLst = new ArrayList<UserLuckOrderEntity>();
        while (firstOrder != null && max > 0) {
            var flag = false;
            try {
                flag = this.matchChoice(firstOrder, resp);
            } catch (Exception e) {
                log.error(" rate matchChoice error", e);
            }
            var allOrders = this.userLuckOrderDao.getAll(firstOrder.getUserId(), LuckType.Rate, Max);
            UserLuckOrderEntity finalFirstOrder = firstOrder;
            allOrders.removeIf(item -> item.getId() == finalFirstOrder.getId());
            allOrders.add(Def.Zero, firstOrder);

            if (flag) {
                this.successLst.removeIf(item -> item.getId() == finalFirstOrder.getId());
                this.successLst.add(Def.Zero, firstOrder);
                WSMgr.broadcast(TcpCmd.SOLRate, null);
            } else {
                var noRewardRateDto = SOLRewardRateDto.of(firstOrder.getParam(), LuckType.TonRate, firstOrder.getHash(), firstOrder.getChoice(), 0, LuckType.TonRate);
                WSMgr.push(firstOrder.getUserId(), TcpCmd.UnRateReward, noRewardRateDto);
            }
            firstOrder = deque.pollFirst();
            max--;
        }
        //失败的订单重新加入队列
        //deque.addAll(failLst);
    }

    private void pushTon(long userId) {
        UserActiveRewardEntity rewardEntity = this.userActiveRewardDao.getInfoById(userId);
        var dto = RaceValDto.ofTon(rewardEntity.getTonVal(), LuckType.TonRate);
        WSMgr.push(userId, TcpCmd.TonVal, dto);
    }

    private void pushReward(long userId, long bet, String hash, String choice, long payout) {
        var dto = SOLRewardRateDto.of(bet, LuckType.TonRate, hash, choice, payout, LuckType.TonRate);
        WSMgr.push(userId, TcpCmd.RateReward, dto);
    }

    public boolean matchChoice(UserLuckOrderEntity order, SlotResp resp) {
        order.setHighVal(resp.getSlot());
        order.setHash(resp.getResult().getBlockhash());
        //修改订单状态
        order.setStatus(Def.True);
        //中奖字符串
        String btcStr = resp.getResult().getBlockhash().substring(resp.getResult().getBlockhash().length() - 1);
        //中了大奖
        UserActiveRewardEntity rewardEntity = this.userActiveRewardDao.getInfoById(order.getUserId());

        //检查是否中了数字
        if ("1".equals(order.getChoice())) {
            if (NumberUtil.isNumber(btcStr)) {
                this.reward(order, rewardEntity, 6 * (order.getParam()), order.getParam(), order.getParam() * 6);
                return true;
            }
        } else {
            //检查是否小写
            if ("2".equals(order.getChoice())) {
                boolean isLowerCase = (btcStr.charAt(0) >= 'a' && btcStr.charAt(0) <= 'z');
                if (isLowerCase) {
                    this.reward(order, rewardEntity, 2 * (order.getParam()), order.getParam(), order.getParam() * 2);
                    return true;
                }

            } else {
                //检查是否大写
                if ("3".equals(order.getChoice())) {
                    boolean isUpperCase = (btcStr.charAt(0) >= 'A' && btcStr.charAt(0) <= 'Z');
                    if (isUpperCase) {
                        this.reward(order, rewardEntity, 2 * (order.getParam()), order.getParam(), order.getParam() * 2);
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private void reward(UserLuckOrderEntity order, UserActiveRewardEntity rewardEntity, long val, long bet, long payout) {
        rewardEntity.addTonVal(val);
        order.setTonVal(val);
        this.successLst.add(order);
        pushReward(order.getUserId(), bet, order.getHash(), order.getChoice(), payout);
        pushTon(order.getUserId());
    }

}
